'''

1、通过程序，扫描被进程占用的端口（TCP及UDP）和进程相关信息，并输出。
2、输出结果包括：进程id（pid）、进程名、占用端口、端口类型、Local IP、进程地址。


思路： 先获取tcp、udp占用的端口是哪些，然后打印他们的端口对应的进程信息
'''

import socket

# psutil 是 Python 编程语言的一个进程和系统工具集模块，通过使用 psutil，可以获取操作系统中进程的相关信息。
import psutil


from threading import Thread


def get_ip():
    ip_list = []
    try:
        host_name = socket.gethostname()

        Address = socket.getaddrinfo(host_name,None,family=socket.AF_INET)

    except Exception as e:
        print(e)

    finally:
        for item in Address:
            ip_list.append(item[4][0])

        ip_list.append('0.0.0.0')
        ip_list.append('127.0.0.1')
        return ip_list

# 返回进程信息的函数,传入端口号
def return_process_msg(port):

    """根据端口寻找该进程对应的pid"""
    pids = []
    # 获取当前的网络连接信息
    net_con = psutil.net_connections()
    # 将端口号相对应的的进程id，存到pid列表里面
    for con_info in net_con:
        if con_info.laddr.port == port:
            pids.append(con_info.pid)

    # 通过pid获取进程信息
    for pid in pids:
        # 进程对象
        p = psutil.Process(pid)
        # 进程名
        process_name = p.name()

        return "进程名: %-20s  pid: %-6s  占用端口：%-5s   Local IP: %-20s " % \
               (process_name, pid,port,IP,)


# 获取local ip
hostname = socket.gethostname()
IP = socket.gethostbyname(hostname)


# TCP扫描端口
def scanning_tcp_port(ip,port):

    """
    扫描判断tcp端口是否占用,记录被占用的tcp端口
    """

    # 创建socket对象s，这里网络层协议是ip协议，传输层协议是tcp协议
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 设置超时时间为1s
        s.settimeout(1)
        # s.connect_ex()  connect()函数的扩展版本,,而不是抛出出错时返回出错码异常
        resp = s.connect_ex((ip,port))
        if resp == 0:
            result = return_process_msg(port)
            print(result, ' 端口类型：%-5s 端口对应的ip：%s' % ('TCP', ip))
    except Exception as e :
        print(e)

    finally:
        # 关闭socket
        s.close()


def scanning_udp_port(ip,port):
    # 字节数
    BufLen = 1024

    try:
        # 创建socket对象
        udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置超时时间
        udp_client.settimeout(1)

        # 发送一句话用来后面获取端口
        to_Send = 'hello'

        udp_client.sendto(to_Send.encode(),(ip,port))

        # 接收返回的数据
        udp_client.recv(BufLen)

    except Exception as e:
        # 如果超时没有得到回复则说明端口开放
        if e.args[0] == 'timed out':
            result = return_process_msg(port)
            print(result, ' 端口类型：UDP')

    finally:
        # 关闭socket服务
        udp_client.close()


# 开启多线程处理，传入函数名，扫描65535个端口
def return_all(target):
    for i in range(0, 7):
        ip2 = get_ip()[i]
        for port in range(1,65536):
            thread = Thread(target=target,args=(ip2,port,))
            thread.start()
        for i in range(1,65536):
            thread.join()


if __name__ == "__main__":
    print('端口开启的所有TCP端口进程信息:')
    return_all(scanning_tcp_port)
    print('-------------------------------------------------------------------------------------------')
    print('端口开启的所有UDP端口进程信息:')
    # return_all(scanning_udp_port)
    # for i in range(0, 7):
    #     ip2 = get_ip()[i]
    #     print(ip2)


